using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Reflection;
using Borg;

public static class Program
{
    private static readonly Dictionary<string, string> ShortForms = new Dictionary<string, string>();

    static Program()
    {
        ShortForms.Add("ver", "version");
        ShortForms.Add("?", "help");
        ShortForms.Add("h", "help");
        ShortForms.Add("v", "verbosity");
        ShortForms.Add("q", "quiet");
        ShortForms.Add("m", "minimal");
        ShortForms.Add("n", "normal");
        ShortForms.Add("d", "detailed");
        ShortForms.Add("diag", "diagnostic");
        ShortForms.Add("sp", "startupPath");
        ShortForms.Add("lp", "loaderPath");
        ShortForms.Add("lf", "logfile");
        ShortForms.Add("nl", "nologo");
        ShortForms.Add("args", "arguments");
        ShortForms.Add("cad", "createappdomain");
        ShortForms.Add("trace", "trace");
        ShortForms.Add("appName", "applicationName");
        ShortForms.Add("appCompany", "applicationCompany");
        ShortForms.Add("appVersion", "applicationVersion");
    }

    [STAThread]
    public static void Main(string[] args)
    {
        var options = GetOptions(args);
        
        if (options.ContainsKey("version"))
            Console.WriteLine(Runtime.VersionInfo);
        else if (options.ContainsKey("help"))
        {
            Console.WriteLine(Runtime.VersionInfo);
            WriteHelp();
        }
        else
        {
            var initializer = new Initializer();
            initializer.ApplyAssemblyInfo(Assembly.GetExecutingAssembly());
            initializer.Dispatcher.Type = "Borg.Logging.DesktopDispatcher, Borg.Logging.Library";

            var configSection =
                ConfigurationManager.OpenExeConfiguration(
                ConfigurationUserLevel.None).Sections["borg"] as Borg.Config.ConfigurationSection;
            
            if (configSection != null)
                initializer.ApplyConfig(configSection);

            if (options.ContainsKey("createappdomain"))
            {
                var result = true;
                
                if (!string.IsNullOrEmpty(options["createappdomain"]) && !bool.TryParse(options["createappdomain"], out result))
                    PrintErrorAndQuit(string.Format("Invalid createappdomain value: '{0}'", options["createappdomain"]));
                
                initializer.CreateAppDomain = result;
            }

            if (options.ContainsKey("applicationName"))
                initializer.Application.Name = options["applicationName"];
            
            if (options.ContainsKey("applicationCompany"))
                initializer.Application.Company = options["applicationCompany"];
            
            if (options.ContainsKey("applicationVersion"))
                initializer.Application.Version = options["applicationVersion"];
            
            if (options.ContainsKey("loaderPath"))
            {
                initializer.LoaderPaths.Clear();
                initializer.LoaderPaths.AddRange(
                    options["loaderPath"].Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries));
            }

            if (options.ContainsKey("startupPath"))
            {
                initializer.StartupPaths.Clear();
                initializer.StartupPaths.AddRange(
                    options["startupPath"].Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries));
            }

            if (options.ContainsKey("arguments"))
                foreach (var pair in GetArguments(options["arguments"]))
                    initializer.Arguments[pair.Key] = pair.Value;

            if (options.ContainsKey("verbosity"))
                initializer.Verbosity =
                    (VerbosityLevel)Enum.Parse(typeof(VerbosityLevel), options["verbosity"], true);

            List<LoggerConfig> loggers;
            LoggerConfig logger;

            if (options.ContainsKey("logfile"))
            {
                loggers = initializer.Loggers;
                logger = new LoggerConfig
                             {
                                 Type = "Borg.Logging.FileLogger, Borg.Logging.Library",
                                 Path = options["logfile"]
                             };

                loggers.Insert(0, logger);
            }

            loggers = initializer.Loggers;
            logger =
                new LoggerConfig
                    {
                        Type = "Borg.Logging.ConsoleLogger, Borg.Logging.Library"
                    };

            loggers.Insert(0, logger);
            initializer.CreateAndStartRuntime();
        }
    }

    private static Dictionary<string, string> GetOptions(IEnumerable<string> args)
    {
        var options =
            new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);

        foreach (var arg in args)
        {
            int index;
            
            if (arg.Length > 1)
                index = arg.IndexOfAny(new[] {'/', '-'}, 0, 1) != 0 ? 1 : 0;
            else
                index = 1;

            if (index == 0)
            {
                var strings = arg.Substring(1).Split(new[] {':'});

                var key =
                    ShortForms.ContainsKey(strings[0])
                        ? ShortForms[strings[0]]
                        : strings[0];
                
                var opt = string.Empty;
                
                if (strings.Length > 1)
                    opt =
                        ShortForms.ContainsKey(strings[1])
                            ? ShortForms[strings[1]]
                            : strings[1];
                
                options[key] = opt;
                
                if (!ShortForms.ContainsValue(key))
                    PrintErrorAndQuit(
                        opt != string.Empty
                        ? string.Format("Invalid start parameter: option \"{0}:{1}\" not found", key, opt)
                        : string.Format("Invalid start parameter: option \"{0}\" not found", key));
            }
            else
                PrintErrorAndQuit(string.Format(
                    "Invalid start parameter: {0}\n  Parameter must start with '/' or '-'.", arg));
        }

        if (options.ContainsKey("logfile") && string.IsNullOrEmpty(options["logfile"]))
            PrintErrorAndQuit("Filename of logfile must not be empty.");

        return options;
    }

    private static IEnumerable<KeyValuePair<string, string>> GetArguments(string arguments)
    {
        try
        {
            var strings = arguments.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries);
            var pairs = new List<KeyValuePair<string, string>>(strings.Length);
            
            pairs.AddRange(from s in strings
                           let chs = new[] {'='}
                           select s.Split(chs) into str
                           select new KeyValuePair<string, string>(str[0], str[1]));

            return pairs;
        }
        catch
        {
            PrintErrorAndQuit(string.Format("Invalid argument string: '{0}'", arguments));
            return null;
        }
    }

    private static void PrintErrorAndQuit(string errorMessage)
    {
        Console.WriteLine(Runtime.VersionInfo);

        var foregroundColor = Console.ForegroundColor;
        Console.ForegroundColor = ConsoleColor.Red;

        Console.WriteLine(errorMessage);
        Console.ForegroundColor = foregroundColor;
        Console.WriteLine("Press ENTER to exit...");
        Console.ReadLine();

        Environment.Exit(1);
    }

    private static void WriteHelp()
    {
        Console.WriteLine("\r\n" +
                          "Syntax:              Borg.Runtime.exe [options]\r\n" +
                          "\r\nDescription:         Launches the Borg runtime.\r\n" +
                          "\r\n" +
                          "Switches:\r\n" +
                          "\r\n" +
                          "  /help              Display this usage message. (Short form: /? or /h)\r\n" +
                          "\r\n" +
                          "  /nologo            Do not display the startup banner and copyright message.\r\n" +
                          "\r\n" +
                          "  /version           Display version information only. (Short form: /ver)\r\n" +
                          "\r\n" +
                          "  /process:<name>    Use process with given name.\r\n" +
                          "\r\n" +
                          "  /processid:<id>    Use process with given id.\r\n" +
                          "\r\n" +
                          "  /verbosity:<level> Display this amount of information in the event log.\r\n" +
                          "                     The available verbosity levels are: q[uiet], m[inimal],\r\n" +
                          "                     n[ormal], d[etailed], and diag[nostic]. (Short form: /v)\r\n" +
                          "                     Example:\r\n" +
                          "                       /verbosity:quiet\r\n" +
                          "\r\n" +
                          "  /discovery:<paths> Files and directories for initial discovery of contracts\r\n" +
                          "                     and plugins. Separate paths by ';'. Default are all \r\n" +
                          "                     assemblies in the base directory of the application. \r\n" +
                          "                     (Short form: /dis)\r\n" +
                          "                     Example:\r\n" +
                          "                       /dis:Borg.Assimilator.dll;contracts\\;plugins\\Monitor.dll;\r\n" +
                          "\r\n" +
                          "  /base:<paths>      Directories which are registered in the ApplicationBase to\r\n" +
                          "                     discover plugins and contracts from there. Separate paths\r\n" +
                          "                     by ';'. Default is the base directory of the application. \r\n" +
                          "                     Example:\r\n" +
                          "                       /base:.\\;plugins\\;contracts\\\r\n" +
                          "\r\n" +
                          "  /logfile:<path>    The Logger writes the messages in the specified file.\r\n" +
                          "                     Example:\r\n" +
                          "                       /logfile:.\\logfile.txt\r\n" +
                          "\r\n" +
                          "  /createAppDomain   Create a new app domain on start up.");
    }
}